<?php


namespace App\Commons\WechatOpenPlatform;


use App\Commons\WechatOpenPlatform\Exceptions\WechatOpenPlatformException;
use App\Models\Wechat\MiniProgramOperationRecord;
use App\Repositories\Wechat\MiniProgramOperationRecordRepository;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;

class WechatOpenPlatformMiniProgramTool {
    /**
     * 一般正确请求 errcode 值 {errcode: 0, errmsg: "ok"}
     */
    const SUCCESS_RESPONSE_ERROR_CODE = 0;

    private $_miniProgram;

    /**
     * @var \App\Models\App\AppProject
     */
    private $_projectEl;

    /**
     * @var \EasyWeChat\OpenPlatform\Application
     */
    private $_openPlatformApp;

    /**
     * @var string
     */
    private $_appId ;

    /**
     * EasyWechatOpenPlatformMiniProgramTool constructor.
     *
     * @param \EasyWeChat\OpenPlatform\Application                      $openPlatformApp
     * @param                                                           $appId
     * @param null                                                      $refreshToken
     * \App\Models\App\AppProject                                       $projectEl
     * @param null|\EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken $accessToken
     */
    public function __construct($openPlatformApp, $appId, $refreshToken = null, $projectEl = null, $accessToken = null) {
        $this->_miniProgram     = $openPlatformApp->miniProgram($appId, $refreshToken, $accessToken);
        $this->_projectEl       = $projectEl;
        $this->_openPlatformApp = $openPlatformApp;
        $this->_appId           = $appId;
    }

    /**
     * @return \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application
     */
    public function getMiniProgram() {
        return $this->_miniProgram;
    }

    /**
     * @param \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application $miniProgram
     *
     * @return mixed
     */
    public function setMiniProgram($miniProgram) {
        return $this->_miniProgram = $miniProgram;
    }

    /**
     * 上传小程序代码
     *
     * @param int    $templateId
     * @param string $extJson
     * @param string $version
     * @param string $description
     *
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\Exception
     */
    public function miniProgramTemplateCommit(int $templateId, string $extJson, string $version, string $description) {

        return $this->checkWxResponseContent(
            [
                'template_id' => $templateId,
                'ext_json'    => $extJson,
                'version'     => $version,
                'description' => $description,
            ],
            '上传小程序代码',
            function () use ($templateId, $extJson, $version, $description) {
                return $this->getMiniProgram()->code->commit($templateId, $extJson, $version, $description);
            }
        );
    }

    /**
     * 小程序提交审核
     *
     * @param array $itemList
     *
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\Exception
     */
    public function miniProgramSubmitAudit(array $itemList) {
        return $this->checkWxResponseContent(
            $itemList,
            '小程序提交审核',
            function () use ($itemList) {
                return $this->getMiniProgram()->code->submitAudit($itemList);
            },
            function ($responseInfo) {
                if(isset($responseInfo['errcode'])){
                    switch ($responseInfo['errcode']) {
                        case 85009:
                            //如果当前存在审核项目，则取消该审核项目，重新发布审核
                            $this->storeOperationRecord([
                                'op_name'   => '小程序提交审核失败，已经有正在审核的版本',
                                'result'    => json_encode($responseInfo),
                                'op_result' => \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_FAIL
                            ]);
                            throw new WechatOpenPlatformException(1200002);
                            break;
                        case 47001:
                            //$itemList 组织出错，数据格式错误
                            $this->storeOperationRecord([
                                'op_name'   => '小程序提交审核失败，数据格式错误',
                                'result'    => json_encode($responseInfo),
                                'op_result' => \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_FAIL
                            ]);
                            throw new WechatOpenPlatformException(1200005);
                    }
                }

                throw new WechatOpenPlatformException(1200001);
            }
        );
    }

    /**
     * 小程序审核撤回
     * @return mixed
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\Exception
     */
    public function withdrawAudit() {
        return $this->checkWxResponseContent(
            null,
            '小程序审核撤回',
            function () {
                return $this->getMiniProgram()->code->withdrawAudit();
            },
            function ($responseInfo) {
                if (isset($responseInfo['errcode'])) {
                    switch ($responseInfo['errcode']) {
                        case 87013:
                            //小程序审核撤回失败，撤回次数达到上限，（每天一次，每个月 10 次）
                            $this->storeOperationRecord([
                                'op_name'   => '小程序审核撤回失败，撤回次数达到上限',
                                'result'    => json_encode($responseInfo),
                                'op_result' => \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_FAIL
                            ]);
                            throw new WechatOpenPlatformException(1200006);
                    }
                }

                throw new WechatOpenPlatformException(1200001);
            }
        );
    }

    /**
     * 查询最新一次提交的审核状态
     * @return mixed
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\Exception
     */
    public function getLatestAuditStatus() {
        return $this->checkWxResponseContent(
            null,
            '查询最新一次提交的审核状态',
            function () {
                return $this->getMiniProgram()->code->getLatestAuditStatus();
            }
        );
    }

    /**
     * 查询指定发布审核单的审核状态
     *
     * @param $auditId
     *
     * @return mixed
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\Exception
     */
    public function getAuditStatus($auditId) {
        return $this->checkWxResponseContent(
            [
                'audit_id' => $auditId,
            ],
            '查询指定发布审核单的审核状态',
            function () use ($auditId) {
                return $this->getMiniProgram()->code->getAuditStatus($auditId);
            }
        );
    }

    /**
     * 发布已通过审核的小程序
     * @return mixed
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\Exception
     */
    public function release() {
        return $this->checkWxResponseContent(
            null,
            '发布已通过审核的小程序',
            function () {
                return $this->getMiniProgram()->code->release();
            },
            function ($responseInfo) {
                if (isset($responseInfo['errcode'])) {
                    switch ($responseInfo['errcode']) {
                        case 85019:
                            //现网已经在灰度发布，不能进行版本回退
                            $this->storeOperationRecord([
                                'op_name'   => '发布已通过审核的小程序失败，没有审核版本',
                                'result'    => json_encode($responseInfo),
                                'op_result' => \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_FAIL
                            ]);
                            throw new WechatOpenPlatformException(1200007);
                            break;
                        case 85020:
                            //现网已经在灰度发布，不能进行版本回退
                            $this->storeOperationRecord([
                                'op_name'   => '发布已通过审核的小程序失败，审核状态未满足发布',
                                'result'    => json_encode($responseInfo),
                                'op_result' => \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_FAIL
                            ]);
                            throw new WechatOpenPlatformException(1200008);
                            break;
                    }
                }
                throw new WechatOpenPlatformException(1200001);
            }
        );
    }

    /**
     * 版本回退
     * @return mixed
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\Exception
     */
    public function rollbackRelease() {
        return $this->checkWxResponseContent(
            null,
            '版本回退',
            function () {
                return $this->getMiniProgram()->code->rollbackRelease();
            },
            function ($responseInfo) {
                if (isset($responseInfo['errcode'])) {
                    switch ($responseInfo['errcode']) {
                        case 87011:
                            //现网已经在灰度发布，不能进行版本回退
                            $this->storeOperationRecord([
                                'op_name'   => '版本回退失败，现网已经在灰度发布，不能进行版本回退',
                                'result'    => json_encode($responseInfo),
                                'op_result' => \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_FAIL
                            ]);
                            throw new WechatOpenPlatformException(1200004);
                            break;
                    }
                }
                throw new WechatOpenPlatformException(1200001);
            }
        );
    }

    /**
     * 取消分阶段发布
     * @return mixed
     * @throws \Exception
     */
    public function revertGrayRelease() {
        /**
         * https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/revertgrayrelease.html
         * 在小程序分阶段发布期间，可以随时调用本接口取消分阶段发布。取消分阶段发布后，受影响的微信用户（即被灰度升级的微信用户）的小程序版本将回退到分阶段发布前的版
         */
        return $this->checkWxResponseContent(
            null,
            '取消分阶段发布',
            function () {
                return $this->getMiniProgram()->code->revertGrayRelease();
            }
        );
    }

    /**
     * 设置服务器域名
     *
     * @param array $params
     *
     * @return mixed
     * @throws \Exception
     */
    public function domainModify(array $params) {
        return $this->checkWxResponseContent(
            $params,
            '设置服务器域名',
            function () use ($params) {
                return $this->getMiniProgram()->domain->modify($params);
            }
        );
    }

    /**
     * 设置业务域名
     *
     * @param array  $domains
     * @param string $action
     *
     * @return mixed
     * @throws \Exception
     */
    public function setWebviewDomain(array $domains, $action = 'add') {
        return $this->checkWxResponseContent(
            [
                'domains' => $domains,
                'action'  => $action,
            ],
            '设置业务域名',
            function () use ($domains, $action) {
                return $this->getMiniProgram()->domain->setWebviewDomain($domains, $action);
            }
        );
    }

    /**
     * 获取体验版二维码
     * https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/get_qrcode.html
     *
     * @param string $path
     *
     * @return mixed|\EasyWeChat\Kernel\Http\Response
     * @throws \Exception
     */
    public function getQrCode(string $path = null) {
        $requestName = '获取体验版二维码';

        //操作准备记录
        $this->storeOperationRecord([
            'op_name'   => $requestName . '操作准备',
            'result'    => json_encode(['path' => $path]),
            'op_result' => \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_NONE,
        ]);

        try {
            //请求二维码
            $getQrCodeResponse = $this->getMiniProgram()->code->getQrCode($path);
            $getQrCodeResult   = json_decode($getQrCodeResponse->getBodyContents(), true);

            //请求是否异常,非异常则返回图片二进制
            $isSuccess = !isset($getQrCodeResult['errcode']);
        } catch (\Exception $e) {
            Log::error('获取体验版二维码' . $e->getMessage());
            Log::error($e->getTraceAsString());
            $getQrCodeResult = ['请求时发生错误', $e->getMessage()];
            $isSuccess       = false;
        }

        //操作结果记录
        $this->storeOperationRecord([
            'op_name'   => $requestName,
            'result'    => json_encode($isSuccess ? null : $getQrCodeResult),
            'op_result' => $isSuccess ? MiniProgramOperationRecord::OP_RESULT_SUCCESS : MiniProgramOperationRecord::OP_RESULT_FAIL,
        ]);

        //如果获取异常直接抛出异常
        if (!$isSuccess) {
            throw new WechatOpenPlatformException(1200003);
        }

        //如果获取成功直接将返回的二进制结果
        return $getQrCodeResponse;
    }

    /**
     * 加急审核申请
     *
     * @param $auditId
     *
     * @return mixed
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException|\Exception
     */
    public function speedUpAudit($auditId) {
        return $this->checkWxResponseContent(
            [
                'audit_id' => $auditId,
            ],
            '加急审核申请',
            function () use ($auditId) {
                return $this->getMiniProgram()->code->httpPostJson('wxa/speedupaudit', [
                    'auditid' => $auditId,
                ]);
            }
        );
    }

    /**
     * 查询服务商的当月提审限额（quota）和加急次数
     * @return mixed
     * @throws \Exception
     */
    public function queryQuota() {
        return $this->checkWxResponseContent(
            null,
            '查询服务商的当月提审限额（quota）和加急次数',
            function () {
                return $this->getMiniProgram()->code->httpPostJson('wxa/queryquota');
            }
        );
    }

    /**
     * 求情检测
     * 一般请求成功返回为 {errcode: 0, errmsg: "ok"}
     *
     * @param $requestName
     * @param $responseInfo
     * @param $extErrorDealFn
     * @param $extSuccessFn
     *
     * @return mixed
     * @throws \Exception
     */
    public function checkWxResponseContent($requestPrepareData, $requestName,callable $responseGetFn, $extErrorDealFn = null, $extSuccessFn = null) {
        //请求操作前信息记录
        !is_null($requestPrepareData) && $this->_recordOperationPrepare($requestPrepareData, $requestName);

        //执行请求
        try {
            $responseInfo = call_user_func_array($responseGetFn, []);
        } catch (\Exception $e) {
            Log::error("发起微信 [ {$requestName} ] 请求出错:" . $e->getMessage());
            Log::error($e->getTraceAsString());
            $responseInfo = ['请求异常，此为异常捕获记录,请从日志中查看', $e->getMessage()];
        }

        if (env('APP_ENV') !== 'production') {
            Log::info("发起微信 [ {$requestName} ] 请求,请求返回:");
            Log::info($responseInfo);
        }

        $operationData = [
            'op_name' => $requestName,
            'result'  => json_encode($responseInfo),
        ];

        if (isset($responseInfo['errcode']) && $responseInfo['errcode'] == self::SUCCESS_RESPONSE_ERROR_CODE) {

            $operationData['wx_auditid'] = $responseInfo['auditid'] ?? '';//提交审核中带有的审核编号
            $operationData['op_result']  = \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_SUCCESS;
            $this->storeOperationRecord($operationData);

            if (is_callable($extSuccessFn)) {
                return call_user_func_array($extSuccessFn, [$responseInfo]);
            }

            return $responseInfo;
        } else {
            Log::error("微信 [ {$requestName} ] 请求出错,请求返回异常:");
            Log::error($responseInfo);

            $operationData['op_result'] = \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_FAIL;
            $this->storeOperationRecord($operationData);

            if (is_callable($extErrorDealFn)) {
                return call_user_func_array($extErrorDealFn, [$responseInfo]);
            }

            throw new WechatOpenPlatformException(1200001);
        }
    }

    /**
     * @return array
     */
    protected function getOperationRecordBaseInfo(){
        return [
            'user_id'             => Auth::check() ? Auth::user()->id : 0,
            'ap_id'               => $this->_projectEl ? $this->_projectEl->id : 0,
            'open_appid'          => $this->_projectEl ? $this->_projectEl->open_appid : '',
            'wx_authorizer_appid' => $this->_projectEl ? $this->_projectEl->wx_authorizer_appid : '',
            'mpt_id'              => $this->_projectEl ? $this->_projectEl->mpt_id : 0,
        ];
    }

    /**
     * @param $extStoreInfo
     *
     * @return \Illuminate\Database\Eloquent\Model
     */
    protected function storeOperationRecord($extStoreInfo){
        return MiniProgramOperationRecordRepository::getInstance()->store($this->getOperationRecordBaseInfo() + $extStoreInfo);
    }

    /**
     * 请求操作准备记录
     *
     * @param $requestPrepareData
     * @param $requestName
     */
    protected function _recordOperationPrepare($requestPrepareData, $requestName) {
        $this->storeOperationRecord([
            'op_name'   => $requestName . '操作准备',
            'result'    => json_encode($requestPrepareData),
            'op_result' => \App\Models\Wechat\MiniProgramOperationRecord::OP_RESULT_NONE,
        ]);
    }

    /**
     * 查询小程序用户隐私保护指引
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     */
    public function getPrivacy(){
        return $this->getMiniProgram()->privacy->get();
    }

    /**
     * 配置小程序用户隐私保护指引
     * @param $param
     * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function setPrivacy($param){
        return $this->getMiniProgram()->privacy->set($param);
    }
}
